package com.tjvib.util.Manager;

import android.os.Handler;
import android.os.Looper;

import com.tjvib.bean.DataSetInfo;
import com.tjvib.bean.ResBean;
import com.tjvib.common.LoadingCallback;
import com.tjvib.sensor.LocalSensorData;
import com.tjvib.sensor.LpmsB2SensorData;
import com.tjvib.sensor.SensorData;
import com.tjvib.sensor.ZeroLabSensorData;
import com.tjvib.util.LogUtil;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DataSetManager {

    static {
        System.loadLibrary("TJVIB2");
    }

    private static DataSetManager instance = new DataSetManager();

    private DataSetInfo dataSetInfo;
    private List<? extends SensorData> dataSetContent;

    private DataSetManager() {
    }

    public static DataSetManager getInstance() {
        return instance;
    }

    public DataSetInfo getDataSetInfo() {
        return dataSetInfo;
    }

    public void setDataSetInfo(DataSetInfo dataSetInfo) {
        this.dataSetInfo = dataSetInfo;
    }

    public List<? extends SensorData> getDataSetContent(int imuId) {
        List<SensorData> result = new ArrayList<>();
        float temp_timestamp = 0;
        if (imuId == -1) {
            for (int i = 1; i < dataSetContent.size(); i++) {
                LocalSensorData data = (LocalSensorData) dataSetContent.get(i);
                if (temp_timestamp < data.timestamp) {
                    result.add(data);
                    temp_timestamp = data.timestamp;
                }
            }
            return result;
        }
        for (int i = 1; i < dataSetContent.size(); i++) {
            LpmsB2SensorData data = (LpmsB2SensorData) dataSetContent.get(i);
            if (data.imuId == imuId && temp_timestamp < data.timestamp) {
                result.add(data);
                temp_timestamp = data.timestamp;
//                LogUtil.e("data="+data.imuId+" "+data.timestamp+" "+data.localtime+" "+data.acc[0]);
//            }else{
//                LogUtil.w("data="+data.imuId+" "+data.timestamp+" "+data.localtime+" "+data.acc[0]);
            }
        }
        return result;
    }

    public void setDataSetContent(List<SensorData> dataSetContent) {
        this.dataSetContent = dataSetContent;
    }

    public void setDataSetContent(String json) throws JSONException {
        JSONArray contentArray = new JSONArray(json);
        if (dataSetInfo.getType().equals("local")) {
            List<LocalSensorData> contentList = new ArrayList<>();
            for (int i = 0; i < contentArray.length(); i++) {
                JSONObject dataObject = contentArray.getJSONObject(i);
                LocalSensorData item = new LocalSensorData(dataObject);
                contentList.add(item);
            }
            dataSetContent = getRemoveInfluenceLOCAL(contentList);
//        } else if (dataSetInfo.getType().equals("zerolab")) {
//            List<ZeroLabSensorData> contentList = new ArrayList<>();
//            for (int i = 0; i < contentArray.length(); i++) {
//                JSONObject dataObject = contentArray.getJSONObject(i);
//                ZeroLabSensorData item = new ZeroLabSensorData(dataObject);
//                contentList.add(item);
//            }
//            dataSetContent = getRemoveInfluenceZEROLAB(contentList);
        } else if (dataSetInfo.getType().equals("lpmsb2")) {
            List<LpmsB2SensorData> contentList = new ArrayList<>();
            for (int i = 0; i < contentArray.length(); i++) {
                JSONObject dataObject = contentArray.getJSONObject(i);
                LpmsB2SensorData item = new LpmsB2SensorData(dataObject);
                contentList.add(item);
            }
            dataSetContent = getRemoveInfluenceLPMSB2(contentList);
        }
    }


    public List<LpmsB2SensorData> getRemoveInfluenceLPMSB2(List<LpmsB2SensorData> ori){
        // 减去均值，去除其他噪声（重力）影响
        List<LpmsB2SensorData> res = ori;
        float[] sum = new float[12];
        Arrays.fill(sum, 0);
        int len = ori.size();
//        float dt = ori.get(2).timestamp - ori.get(1).timestamp;
//        float lastTmp = ori.get(1).timestamp;
        for(int i = 0; i < len; i++){
            SensorData data = ori.get(i);
            sum[0] += data.acc[0];
            sum[1] += data.acc[1];
            sum[2] += data.acc[2];
            sum[3] += data.gyr[0];
            sum[4] += data.gyr[1];
            sum[5] += data.gyr[2];
            sum[6] += data.mag[0];
            sum[7] += data.mag[1];
            sum[8] += data.mag[2];
            sum[9] += data.linAcc[0];
            sum[10] += data.linAcc[1];
            sum[11] += data.linAcc[2];
//            if(i != 0) {
//                if(lastTmp > data.timestamp)
//                    data.timestamp = lastTmp + dt;
//                lastTmp = data.timestamp;
//            }
        }
        for(int i = 0; i < len; i++) {
            res.get(i).acc[0] -= sum[0]/len;
            res.get(i).acc[1] -= sum[1]/len;
            res.get(i).acc[2] -= sum[2]/len;
            res.get(i).gyr[0] -= sum[3]/len;
            res.get(i).gyr[1] -= sum[4]/len;
            res.get(i).gyr[2] -= sum[5]/len;
            res.get(i).mag[0] -= sum[6]/len;
            res.get(i).mag[1] -= sum[7]/len;
            res.get(i).mag[2] -= sum[8]/len;
            res.get(i).linAcc[0] -= sum[9]/len;
            res.get(i).linAcc[1] -= sum[10]/len;
            res.get(i).linAcc[2] -= sum[11]/len;
        }
        return res;
    }


    public List<ZeroLabSensorData> getRemoveInfluenceZEROLAB(List<ZeroLabSensorData> ori){
        // 减去均值，去除其他噪声（重力）影响
        List<ZeroLabSensorData> res = ori;
        float[] sum = new float[12];
        Arrays.fill(sum, 0);
        int len = ori.size();
        float dt = ori.get(2).timestamp - ori.get(1).timestamp;
        float lastTmp = ori.get(1).timestamp;
        for(int i = 0; i < len; i++){
            SensorData data = ori.get(i);
            sum[0] += data.acc[0];
            sum[1] += data.acc[1];
            sum[2] += data.acc[2];
            sum[3] += data.gyr[0];
            sum[4] += data.gyr[1];
            sum[5] += data.gyr[2];
            sum[6] += data.mag[0];
            sum[7] += data.mag[1];
            sum[8] += data.mag[2];
            sum[9] += data.linAcc[0];
            sum[10] += data.linAcc[1];
            sum[11] += data.linAcc[2];
            if(i != 0) {
                if(lastTmp > data.timestamp)
                    data.timestamp = lastTmp + dt;
                lastTmp = data.timestamp;
            }
        }
        for(int i = 0; i < len; i++) {
            res.get(i).acc[0] -= sum[0]/len;
            res.get(i).acc[1] -= sum[1]/len;
            res.get(i).acc[2] -= sum[2]/len;
            res.get(i).gyr[0] -= sum[3]/len;
            res.get(i).gyr[1] -= sum[4]/len;
            res.get(i).gyr[2] -= sum[5]/len;
            res.get(i).mag[0] -= sum[6]/len;
            res.get(i).mag[1] -= sum[7]/len;
            res.get(i).mag[2] -= sum[8]/len;
            res.get(i).linAcc[0] -= sum[9]/len;
            res.get(i).linAcc[1] -= sum[10]/len;
            res.get(i).linAcc[2] -= sum[11]/len;
        }
        return res;
    }


    public List<LocalSensorData> getRemoveInfluenceLOCAL(List<LocalSensorData> ori){
        // 减去均值，去除其他噪声（重力）影响
        List<LocalSensorData> res = ori;
        float[] sum = new float[12];
        Arrays.fill(sum, 0);
        int len = ori.size();
//        float dt = ori.get(2).timestamp - ori.get(1).timestamp;
//        float lastTmp = ori.get(1).timestamp;
        for(int i = 0; i < len; i++){
            SensorData data = ori.get(i);
            sum[0] += data.acc[0];
            sum[1] += data.acc[1];
            sum[2] += data.acc[2];
            sum[3] += data.gyr[0];
            sum[4] += data.gyr[1];
            sum[5] += data.gyr[2];
            sum[6] += data.mag[0];
            sum[7] += data.mag[1];
            sum[8] += data.mag[2];
            sum[9] += data.linAcc[0];
            sum[10] += data.linAcc[1];
            sum[11] += data.linAcc[2];
//            if(i != 0) {
//                if(lastTmp > data.timestamp)
//                    data.timestamp = lastTmp + dt;
//                lastTmp = data.timestamp;
//            }
        }
        for(int i = 0; i < len; i++) {
            res.get(i).acc[0] -= sum[0]/len;
            res.get(i).acc[1] -= sum[1]/len;
            res.get(i).acc[2] -= sum[2]/len;
            res.get(i).gyr[0] -= sum[3]/len;
            res.get(i).gyr[1] -= sum[4]/len;
            res.get(i).gyr[2] -= sum[5]/len;
            res.get(i).mag[0] -= sum[6]/len;
            res.get(i).mag[1] -= sum[7]/len;
            res.get(i).mag[2] -= sum[8]/len;
            res.get(i).linAcc[0] -= sum[9]/len;
            res.get(i).linAcc[1] -= sum[10]/len;
            res.get(i).linAcc[2] -= sum[11]/len;

        }
        return res;
    }

    public List<String> getSensorList() {
        List<String> result = new ArrayList<>();
        if (dataSetInfo.getType().equals("local")) {
            result.add("local");
//        } else if (dataSetInfo.getType().equals("zerolab")) {
//            for (SensorData item : dataSetContent) {
//                ZeroLabSensorData data = (ZeroLabSensorData) item;
//                if (!result.contains(String.valueOf(data.imuId))) {
//                    result.add(String.valueOf(data.imuId));
//                }
//            }
        }else {
            for (SensorData item : dataSetContent) {
                LpmsB2SensorData data = (LpmsB2SensorData) item;
                if (!result.contains(String.valueOf(data.imuId))) {
                    result.add(String.valueOf(data.imuId));
                }
            }
        }
        return result;
    }

    public void cutDataSet(double start, double end) {
        List<SensorData> newList = new ArrayList<>();
        for (SensorData data : dataSetContent) {
            if (start <= data.timestamp && data.timestamp <= end) {
                newList.add(data);
            }
        }
        dataSetContent = newList;
    }

    public float getAccMax(int imuId, int axis) {
        float result = 0f;
        if (imuId == -1) {
            for (SensorData data : dataSetContent) {
                result = Math.max(Math.abs(data.acc[axis]), result);
            }
        } else {
            for (SensorData item : dataSetContent) {
                LpmsB2SensorData data = (LpmsB2SensorData) item;
                if (data.imuId == imuId)
                    result = Math.max(Math.abs(data.acc[axis]), result);
            }
        }
        return result;
    }

    public String getAccMax(int imuId, int axis, int n) {
        return String.format("%."+n+"f", getAccMax(imuId, axis));
    }

    public float getAccMin(int imuId, int axis) {
        float result = Float.MAX_VALUE;
        if (imuId == -1) {
            for (SensorData data : dataSetContent) {
                if (Math.abs(data.acc[axis]) < result) {
                    result = Math.abs(data.acc[axis]);
                }
            }
        } else {
            for (SensorData item : dataSetContent) {
                LpmsB2SensorData data = (LpmsB2SensorData) item;
                if (data.imuId == imuId && Math.abs(data.acc[axis]) < result) {
                    result = Math.abs(data.acc[axis]);
                }
            }
        }
        return result;
    }

    public String getAccMin(int imuId, int axis, int n) {
        return String.format("%."+n+"f", getAccMin(imuId, axis));
    }

//    public double getAccRms(int imuId, int axis) {
//        float SS = 0f;
//        int N = 0;
//        if (imuId == -1) {
//            for (SensorData data : dataSetContent) {
//                SS += data.acc[axis] * data.acc[axis];
//                N++;
//            }
//        } else {
//            for (SensorData item : dataSetContent) {
//                LpmsB2SensorData data = (LpmsB2SensorData) item;
//                if (data.imuId == imuId) {
//                    SS += data.acc[axis] * data.acc[axis];
//                    N++;
//                }
//            }
//        }
//        return Math.sqrt(SS / N);
//    }

//    public String getAccRms(int imuId, int axis, int n) {
//        return String.format("%."+n+"f", getAccRms(imuId, axis));
//    }

    public double getAccRms(double[] signal) {
        double sum = 0.0;
        for (double value : signal)
            sum += value * value;
        return Math.sqrt(sum / signal.length);
    }

    public String runningRMS(int imuId, int axis, double duration, int n) {
        double[][] signal = new double[dataSetContent.size()][2];
        if (imuId == -1) {
            for (int i = 1; i < dataSetContent.size(); i++) {
                SensorData data = dataSetContent.get(i);
                signal[i][0] = data.timestamp;
                signal[i][1] = Double.parseDouble(""+data.acc[axis]);
            }
        } else {
            for (int i = 1; i < dataSetContent.size(); i++) {
                LpmsB2SensorData data = (LpmsB2SensorData) dataSetContent.get(i);
                if (data.imuId == imuId) {
                    signal[i][0] = data.timestamp;
                    signal[i][1] = Double.parseDouble(""+data.acc[axis]);
                }
            }
        }

        int row = signal.length;
        int col = signal[0].length;
        if (col == 1 || signal[row - 1][0] < duration) {
            LogUtil.w("Input data is invalid.");
            return "NaN";
        }

        double dt = (signal[2][0] - signal[0][0])/2;
        int lengthWindow = (int) Math.round(duration / dt);
        double[][] result = new double[row - lengthWindow + 1][2];
        try {
            for (int start = 0; start <= row - lengthWindow; start++) {
                double[] tempSignal = Arrays.copyOfRange(signal[start], 1, lengthWindow + 1);
                double rms = getAccRms(tempSignal);
                result[start][1] = rms;
            }
        } catch (RuntimeException e){
            LogUtil.e("exception="+e + e.getMessage());
            return "NAN";
        }

        double max = Double.MIN_VALUE;
        for(int i = 0; i < result.length; i++)
            max = Math.max(max, Math.abs(result[i][1]));

        return String.format("%."+n+"f", max);
    }


    public String runningRMS(int imuId, int axis, int n) {
        double[][] signal = new double[dataSetContent.size()][2];
        if (imuId == -1) {
            for (int i = 1; i < dataSetContent.size(); i++) {
                SensorData data = dataSetContent.get(i);
                signal[i][0] = data.timestamp;
                signal[i][1] = Double.parseDouble(""+data.acc[axis]);
            }
        } else {
            for (int i = 1; i < dataSetContent.size(); i++) {
                LpmsB2SensorData data = (LpmsB2SensorData) dataSetContent.get(i);
                if (data.imuId == imuId) {
                    signal[i][0] = data.timestamp;
                    signal[i][1] = Double.parseDouble(""+data.acc[axis]);
                }
            }
        }

        int row = signal.length;
        int col = signal[0].length;
        if (col == 1) {
            LogUtil.w("Input data is invalid.");
            return "NaN";
        }

        double duration = Math.abs(signal[signal.length-1][0]-signal[0][0]);
        double dt = (signal[2][0] - signal[0][0])/2;
        int lengthWindow = (int) Math.round(duration / dt);
        double[][] result = new double[row - lengthWindow + 1][2];
        for (int start = 0; start <= row - lengthWindow; start++) {
            double[] tempSignal = Arrays.copyOfRange(signal[start], 1, lengthWindow + 1);
            double rms = getAccRms(tempSignal);
            result[start][1] = rms;
        }

        double max = Double.MIN_VALUE;
        for(int i = 0; i < result.length; i++)
            max = Math.max(max, Math.abs(result[i][1]));

        return String.format("%."+n+"f", max);
    }


    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    double[] fre = new double[3], damp = new double[3], ori_fre = new double[3], ori_damp = new double[3], f = new double[513], p = new double[513];
    public void calculateFreq(int imuId, int axis, double t0, double t1, double H, String struMaterial,
                              LoadingCallback callback) {
        if (callback != null) callback.showLoading();
        executorService.submit(() -> {
            double[] a = new double[dataSetContent.size()];
            int index = 0;
            if (imuId == -1) {
                for (SensorData data : dataSetContent)
                    a[index++] = data.acc[axis];
            } else {
                for (SensorData data : dataSetContent) {
                    LpmsB2SensorData data1 = (LpmsB2SensorData) data;
                    if (data1.imuId == imuId)
                        a[index++] = data.acc[axis];
                }
            }

            char stru;
            if(struMaterial.equals("钢筋混凝土结构")) stru = 'A';
            else if (struMaterial.equals("钢结构")) stru = 'B';
            else stru = 'C'; //砌体结构
            try {
                double[] _res = fre_res1(a, t0, t1, H, stru, fre, ori_fre, damp, ori_damp, f, p);
                ResBean res;
                if(_res == null) res = new ResBean(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(),
                        new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
                else res = new ResBean(fre, ori_fre, damp, ori_damp, p, new double[]{});
                if (callback != null) {
                    new Handler(Looper.getMainLooper()).post(() -> {
                        callback.dismissLoading();
                        callback.onComplete(res);
                    });
                }
            }catch (Exception e){
                LogUtil.e("Error in frequency calculation: "+e.getMessage());
                if (callback!= null) {
                    new Handler(Looper.getMainLooper()).post(() -> {
                        callback.dismissLoading();
                        callback.onError("计算时出错");
                    });
                }
            }
        });
    }


    double[] m = new double[3], mm = new double[144], sm = new double[144],
            ori_m = new double[3], ori_mm = new double[144], ori_sm = new double[144];
    public void calculateMass(int imuId, int axis, int n, double L, double B, double t0, double weight,
                                String struMaterial, double damp, double F1, double F2, double F3,
                              LoadingCallback callback) {
        if (callback != null) callback.showLoading();
        new Thread(() -> {
            List<Double> person_list = new ArrayList<>(), structure_list = new ArrayList<>();
            for (int i = 1; i < dataSetContent.size(); i++) {
                LpmsB2SensorData data = (LpmsB2SensorData) dataSetContent.get(i);
                if(person_list.size() < 2000) {
                    if (data.imuId == imuId) person_list.add(Double.parseDouble("" + data.acc[axis]));
                    else structure_list.add(Double.parseDouble("" + data.acc[axis]));
                }else break;
            }

            int personSize = Math.min(person_list.size(), 2000), structureSize = Math.min(structure_list.size(), 2000);
            double[] person = new double[2000], structure = new double[2000];
            for(int i = 0; i < personSize; i++) person[i] = person_list.get(i);
            for(int i = 0; i < structureSize; i++) structure[i] = structure_list.get(i);
            for(int i = personSize; i < 2000; i++) person[i] = 0.0;
            for(int i = structureSize; i < 2000; i++) structure[i] = 0.0;

            char stru;
            if(struMaterial.equals("框架结构")) stru = 'A';
            else if (struMaterial.equals("剪力墙结构")) stru = 'B';
            else stru = 'C'; //框架剪力墙结构

            double[] _res = mass_res1(person, structure, new double[]{F1, F2, F3}, weight, t0, damp,
                    n, L, B, stru, ori_m, m, ori_mm, mm, ori_sm, sm);

            ResBean res;
            if(_res == null) res = new ResBean(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(),
                    new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
            else res = new ResBean(m, ori_m, mm, ori_mm, sm, ori_sm);
            if (callback != null) {
                new Handler(Looper.getMainLooper()).post(() -> {
                    callback.dismissLoading();
                    callback.onComplete(res);
                });
            }
        }).start();
    }

    public native double[] fre_res1(double[] a, double t0, double t1, double H, char stru_material,
                                    double[] frequency, double[] original_frequency, double[] dampingratio,
                                    double[] original_dampingratio, double[] f, double[] P);

    public native double[] mass_res1(double[] person, double[] structure, double[] f, double weight,
                                     double t0, double d, double n, double L, double B, char stru_system,
                                     double[] original_Modalmass, double[] Modalmass,
                                     double[] original_eachmass, double[] eachmass,
                                     double[] original_k, double[] k);


}
